/**
 * 
 */
package gpl.scotlandyard.services.basics;

import gpl.scotlandyard.beans.basics.Link;
import gpl.scotlandyard.beans.basics.Player;
import gpl.scotlandyard.beans.basics.Ticket;
import gpl.scotlandyard.exceptions.IllegalMoveException;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

/** @author norbert */
public class PlayerManager {

  private static final Logger LOG = Logger.getLogger(PlayerManager.class);

  private BoardManager boardManager;

  /** Creates PlayerManager.
   * @param boardManager */
  public PlayerManager(BoardManager boardManager) {
    boardManager.getClass();
    this.boardManager = boardManager;
    LOG.info("PlayerManager created");
  }

  /** Creates PlayerManager. */
  public PlayerManager() {
    LOG.info("PlayerManager created");
  }

  /** Moves player on this link
   * @param link */
  public void move(Player player, Link link) throws IllegalMoveException {
    link.getClass();

    if (!isValidMove(player, link)) {
      LOG.warn(player.getName() + " invalid link : " + link);
      throw new IllegalMoveException(player, link);
    }

    if (player.isNextMoveHidden()) {
      link = link.createHidden();
    }

    if (player.isDoDoubleMove() && !player.isProceedDoubleMove()) {
      player.setProceedDoubleMove(true);
    } else if (player.isDoDoubleMove() && player.isProceedDoubleMove()) {
      player.setTwice(player.getTwice() - 1);
      player.setDoDoubleMove(false);
      player.setProceedDoubleMove(false);
      if (player.isNextMoveHidden()) {
        player.setNextMoveHidden(false);
      }
    } else if (!player.isDoDoubleMove() && player.isNextMoveHidden()) {
      player.setNextMoveHidden(false);
    }

    if (link.getTicket() == Ticket.NONE) {
      doNoMove(player);
    } else {
      debit(player, link);
      player.moveTo(link);
    }

    LOG.info(player.getName() + " follows link : " + link);
  }

  // LATER next complexity use pattern STRATEGY
  /** Controls validity of the move as follow : <br>
   * Controls from node equals current node. <br>
   * Controls node is not busied. <br>
   * Controls number of tickets if it's a simple or double move.
   * @param link
   * @return if all is ok returns true. */
  private boolean isValidMove(Player player, Link link) {
    if (link.getTicket() != Ticket.START && player.getNode() != null && !player.getNode().equals(link.getFromNode())) {
      return false;
    }
    for (Player detective : boardManager.getBoard().getDetectives()) {
      if (link.getToNode().equals(detective.getNode()) && !detective.equals(player)) {
        return false;
      }
    }

    if (player.isDoDoubleMove() && player.isNextMoveHidden() && player.isProceedDoubleMove()) {
      return player.getHide() > 0 && link.getTicket() == player.getLastUsedTicket();
    } else if (player.isDoDoubleMove() && player.isNextMoveHidden() && !player.isProceedDoubleMove()) {
      return player.getHide() > 1;
    }

    switch (link.getTicket()) {
      case TAXI:
        if (player.isDoDoubleMove()) {
          return player.isProceedDoubleMove() ? player.getTaxi() > 0 && link.getTicket() == player.getLastUsedTicket()
              : player.getTaxi() > 1;
        } else {
          return player.getTaxi() > 0 || player.getHide() > 0;
        }
      case BUS:
        if (player.isDoDoubleMove()) {
          return player.isProceedDoubleMove() ? player.getBus() > 0 && link.getTicket() == player.getLastUsedTicket() : player.getBus() > 1;
        } else {
          return player.getBus() > 0 || player.getHide() > 0;
        }
      case UG:
        if (player.isDoDoubleMove()) {
          return player.isProceedDoubleMove() ? player.getUg() > 0 && link.getTicket() == player.getLastUsedTicket() : player.getUg() > 1;
        } else {
          return player.getUg() > 0 || player.getHide() > 0;
        }
      case HIDE:
        // can occurred with boat moves cause isHiddenMove = false no problem tests ok
        if (player.isDoDoubleMove()) {
          return player.getHide() > 1;
        } else {
          return player.getHide() > 0;
        }
      default: // NONE
        return true;
    }
  }

  /** Returns all available moves looking for isValidMove().
   * <p>
   * If no move can be done offers a NONE move on the same node.
   * </p>
   * 
   * @return List of possible moves. */
  public List<Link> getMoves(Player player) {
    List<Link> moves = new ArrayList<Link>();
    if (!canMove(player)) {
      // only stays on current node
      moves.add(Link.buildNoMove(player.getNode()));
      return moves;
    }

    for (Link link : player.getNode().getLinks()) {
      if (isValidMove(player, link)) {
        moves.add(link);
      }
    }
    LOG.info(player.getName() + " possible moves : " + moves);
    return moves;
  }

  /** Returns true if player can move otherwise false. A player can move if a reachable player.getPosition() looking to
   * its tickets is free.
   * 
   * @return true if player can move otherwise false */
  public boolean canMove(Player player) {
    if (player.getPrevMoves().size() > 0 && player.getNode() == null) {
      throw new IllegalStateException("player.getPosition() is null");
    }
    for (Link link : player.getNode().getLinks()) {
      if (isValidMove(player, link)) {
        return true;
      }
    }
    return false;
  }

  /** Add one to this type of ticket. Only for TAXI, BUS, UG, HIDE.
   * 
   * @param ticket
   * @throws IllegalMoveException */
  public void credit(Player player, Link link) throws IllegalMoveException {
    switch (link.getTicket()) {
      case TAXI:
        player.setTaxi(player.getTaxi() + 1);
        break;
      case BUS:
        player.setBus(player.getBus() + 1);
        break;
      case UG:
        player.setUg(player.getUg() + 1);
        break;
      case HIDE:
        player.setHide(player.getHide() + 1);
        break;
      case START:
        // no advantage
        break;
      case NONE:
        // no advantage
        break;
      default:
        throw new IllegalMoveException(player, link);
    }
  }

  /** @param player
   * @param link
   * @throws IllegalMoveException */
  public void debit(Player player, Link link) throws IllegalMoveException {
    switch (link.getTicket()) {
      case TAXI:
        player.setTaxi(player.getTaxi() - 1);
        break;
      case BUS:
        player.setBus(player.getBus() - 1);
        break;
      case UG:
        player.setUg(player.getUg() - 1);
        break;
      case HIDE:
        player.setHide(player.getHide() - 1);
        break;
      case START:
        // no cost
        break;
      case NONE:
        // no cost
        break;
      default:
        throw new IllegalMoveException(player, link);
    }
  }

  /** Registers no move in the previous moves. Use only when player can't move. 
   * @throws IllegalMoveException */
  private void doNoMove(Player player) throws IllegalMoveException {
    if (canMove(player)) {
      throw new IllegalStateException("player canMove");
    }
    Link noMove = Link.buildNoMove(player.getNode());
    debit(player, noMove);
    player.moveTo(noMove);
    LOG.info("no move : " + noMove);
  }

  /** @param boardManager the boardManager to set */
  public void setBoardManager(BoardManager boardManager) {
    this.boardManager = boardManager;
  }

}
